﻿namespace NJQuery {
	using System;
	using System.Web.UI;
	using System.ComponentModel;
	using System.Collections.Specialized;
	using System.Globalization;

	/// <summary>
	/// Server control used to generate a <see href="http://jqueryui.com/demos/slider/">jQuery UI Slider widget</see> with a two values.
	/// </summary>
	public class SliderRange : SliderBase {


		#region Constants: Property defaults

		private const int defaultStartValue = 0;
		private const int defaultEndValue = 100;
		private const string defaultStartValueDisplayControl = "";
		private const string defaultEndValueDisplayControl = "";

		#endregion


		#region Static Methods: Private routine

		private static void RenderDisplayFieldInitializeScript(ScriptBuilder script, string clientFunction, string displayClientId, string displaySelectorSuffix, string clientId, int index) {
			script.RenderIdSelector(displayClientId, displaySelectorSuffix);
			script.Append(clientFunction);
			script.RenderIdSelector(clientId);
			script.Append(".slider(");
			script.AppendValue("values");
			script.Append(")[");
			script.AppendValue(index);
			script.Append("]);");
		}

		#endregion;


		#region Fields

		private string startValueDisplayControlClientId;
		private string endValueDisplayControlClientId;
		private bool startValueChanged;
		private bool endValueChanged;

		#endregion


		#region Constructors

		/// <summary>
		/// Initializes a new instance of the <see cref="SliderRange" /> class.
		/// </summary>
		public SliderRange() { }

		#endregion


		#region Properties: Widget initialization options

		/// <summary>
		/// Determines the first value of the slider.
		/// This property maps along with <see cref="EndValue" /> to the "values" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultStartValue), Category("Data")]
		public virtual int StartValue {
			get { return ViewState["StartValue"] as int? ?? defaultStartValue; }
			set { ViewState["StartValue"] = value; }
		}

		/// <summary>
		/// Determines the second value of the slider.
		/// This property maps along with <see cref="StartValue" /> to the "values" option when the jQuery UI widget is initialized.
		/// </summary>
		[DefaultValue(defaultEndValue), Category("Data")]
		public virtual int EndValue {
			get { return ViewState["EndValue"] as int? ?? defaultEndValue; }
			set { ViewState["EndValue"] = value; }
		}

		#endregion


		#region Properties: Extended widget functionality

		/// <summary>
		/// Gets or sets reference to a control that can display the first value as it's being manipulated on the client.
		/// </summary>
		[DefaultValue(defaultStartValueDisplayControl), Category("Appearance"), IDReferenceProperty]
		public virtual string StartValueDisplayControl {
			get { return ViewState["StartValueDisplayControl"] as string ?? defaultStartValueDisplayControl; }
			set { ViewState["StartValueDisplayControl"] = value; }
		}

		/// <summary>
		/// Gets or sets reference to a control that can display the second value as it's being manipulated on the client.
		/// </summary>
		[DefaultValue(defaultEndValueDisplayControl), Category("Appearance"), IDReferenceProperty]
		public virtual string EndValueDisplayControl {
			get { return ViewState["EndValueDisplayControl"] as string ?? defaultEndValueDisplayControl; }
			set { ViewState["EndValueDisplayControl"] = value; }
		}

		#endregion


		#region Properties: Sub Control

		/// <summary>
		/// Gets the rendered element ID of the hidden field used to round-trip the <see cref="StartValue"/> property.
		/// </summary>
		public virtual string StartValueClientId {
			get { return string.Format(CultureInfo.InvariantCulture, "__{0}_StartValue", ClientID); }
		}

		/// <summary>
		/// Gets the rendered element ID of the hidden field used to round-trip the <see cref="EndValue" /> property.
		/// </summary>
		public virtual string EndValueClientId {
			get { return string.Format(CultureInfo.InvariantCulture, "__{0}_EndValue", ClientID); }
		}

		#endregion


		#region Events

		/// <summary>
		/// Occurs on postback when the <see cref="StartValue" /> property value has changed since previous render.
		/// </summary>
		[Category("Action")]
		public event EventHandler StartValueChanged;

		/// <summary>
		/// Occurs on postback when the <see cref="EndValue" /> property value has changed since previous render.
		/// </summary>
		[Category("Action")]
		public event EventHandler EndValueChanged;

		#endregion


		#region Methods: Event hooks

		/// <summary>
		/// Raises the <see cref="StartValueChanged"/> event.
		/// </summary>
		/// <param name="e">The <see cref="EventArgs"/> object that contains the event data.</param>
		protected virtual void OnStartValueChanged(EventArgs e) {
			if(StartValueChanged != null) StartValueChanged.Invoke(this, e);
		}

		/// <summary>
		/// Raises the <see cref="EndValueChanged"/> event.
		/// </summary>
		/// <param name="e">The <see cref="EventArgs"/> object that contains the event data.</param>
		protected virtual void OnEndValueChanged(EventArgs e) {
			if(EndValueChanged != null) EndValueChanged.Invoke(this, e);
		}

		#endregion


		#region Methods: Rendering hooks

		/// <summary>
		/// Raises the <see cref="Control.PreRender"/> event.
		/// </summary>
		/// <param name="e">The <see cref="EventArgs"/> object that contains the event data.</param>
		protected override void OnPreRender(EventArgs e) {
			base.OnPreRender(e);

			startValueDisplayControlClientId = this.GetClientId(StartValueDisplayControl);
			endValueDisplayControlClientId = this.GetClientId(EndValueDisplayControl);
		}

		/// <summary>
		/// Sends server control content to a provided <see cref="HtmlTextWriter"/> object, which writes the content to be rendered to the client.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="writer">A <see cref="HtmlTextWriter"/> that represents the output stream to render HTML content on the client.</param>
		protected override void Render(HtmlTextWriter writer) {
			base.Render(writer);

			ClientScriptManager clientScript = Page.ClientScript;
			clientScript.RegisterForEventValidation(new PostBackOptions(this));

			clientScript.RegisterHiddenField(StartValueClientId, StartValue.ToString(CultureInfo.InvariantCulture));
			clientScript.RegisterHiddenField(EndValueClientId, EndValue.ToString(CultureInfo.InvariantCulture));
		}

		/// <summary>
		/// Renders the jQuery "Ready Event" script required to generate the client-side jQuery widget.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A <see cref="ScriptBuilder"/> that creates the output stream to render jQuery JavaScript on the client.</param>
		protected override void RenderReadyScript(ScriptBuilder script) {
			base.RenderReadyScript(script);
			if(!string.IsNullOrEmpty(startValueDisplayControlClientId) || !string.IsNullOrEmpty(endValueDisplayControlClientId))
				RenderDisplayControlInitScript(script);
		}

		/// <summary>
		/// Renders the script for initially sychronizing the value of the <see cref="StartValueDisplayControl"/> and <see cref="EndValueDisplayControl"/>.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A <see cref="ScriptBuilder"/> that creates the output stream to render jQuery JavaScript on the client.</param>
		protected virtual void RenderDisplayControlInitScript(ScriptBuilder script) {

			if(!string.IsNullOrEmpty(startValueDisplayControlClientId))
				RenderDisplayControlInitScript(script, startValueDisplayControlClientId, 0);

			if(!string.IsNullOrEmpty(endValueDisplayControlClientId))
				RenderDisplayControlInitScript(script, endValueDisplayControlClientId, 1);
		}

		/// <summary>
		/// Creates the properties object that will be rendered as the jQuery UI initialization options.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="properties">A <see cref="PropertyCollection"/> object that will become the jQuery UI initialization options.</param>
		protected override void AddInitProperties(PropertyCollection properties) {
			base.AddInitProperties(properties);

			//Always add range for this server control
			properties.AddValue("range", true);

			//Always render values, since true default is null
			properties.AddValue("values", new int[] { StartValue, EndValue });

			ScriptBuilder slideScript = properties.CreateChildScript();
			RenderSlideScript(slideScript);
			if(slideScript.Length != 0) properties.AddScript("slide", slideScript);

		}

		/// <summary>
		/// Renders the client "slide" event script.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="script">A <see cref="ScriptBuilder"/> that creates the output stream to render jQuery JavaScript on the client.</param>
		protected virtual void RenderSlideScript(ScriptBuilder script) {

			script.Append("function(event, ui) {");

			script.Indent++;

			script.NewLine();
			script.RenderIdSelector(StartValueClientId);
			script.Append(".val(ui.values[0]);");

			script.NewLine();
			script.RenderIdSelector(EndValueClientId);
			script.Append(".val(ui.values[1]);");

			if(!string.IsNullOrEmpty(startValueDisplayControlClientId)) {
				script.NewLine();
				script.RenderIdSelector(startValueDisplayControlClientId, ":input");
				script.Append(".val(ui.values[0]);");
				script.NewLine();
				script.RenderIdSelector(startValueDisplayControlClientId, ":not(:input)");
				script.Append(".text(ui.values[0]); ");
			}

			if(!string.IsNullOrEmpty(endValueDisplayControlClientId)) {
				script.NewLine();
				script.RenderIdSelector(endValueDisplayControlClientId, ":input");
				script.Append(".val(ui.values[1]);");
				script.NewLine();
				script.RenderIdSelector(endValueDisplayControlClientId, ":not(:input)");
				script.Append(".text(ui.values[1]);");
			}

			script.Indent--;
			script.NewLine();

			script.Append("}");

		}

		#endregion


		#region Methods: Post data

		/// <summary>
		/// Processes postback data for an ASP.NET server control.
		/// This method is used primarily by control developers.
		/// </summary>
		/// <param name="postDataKey">The key identifier for the control.</param>
		/// <param name="postCollection">The collection of all incoming name values.</param>
		/// <returns>True if the server control's state changes as a result of the postback; otherwise, false.</returns>
		protected override bool LoadPostData(string postDataKey, NameValueCollection postCollection) {

			Page.ClientScript.ValidateEvent(postDataKey);
			bool changed = false;

			int newStartValue;
			if(int.TryParse(postCollection[StartValueClientId], out newStartValue) && newStartValue != StartValue) {
				StartValue = newStartValue;
				changed = startValueChanged = true;
			}

			int newEndValue;
			if(int.TryParse(postCollection[EndValueClientId], out newEndValue) && newEndValue != EndValue) {
				EndValue = newEndValue;
				changed = endValueChanged = true;
			}

			return changed;
		}

		/// <summary>
		/// Signals the server control to notify the ASP.NET application that the state of the control has changed.
		/// This method is used primarily by control developers.
		/// </summary>
		protected override void RaisePostDataChangedEvent() {
			if(startValueChanged || endValueChanged) OnValueChanged(EventArgs.Empty);
			if(startValueChanged) OnStartValueChanged(EventArgs.Empty);
			if(endValueChanged) OnEndValueChanged(EventArgs.Empty);
		}

		#endregion


		#region Methods: Private Routines

		private void RenderDisplayControlInitScript(ScriptBuilder script, string displayClientId, int index) {

			string clientId = ClientID;

			script.NewLine();
			RenderDisplayFieldInitializeScript(script, ".val(", displayClientId, ":input", clientId, index);
			script.NewLine();
			RenderDisplayFieldInitializeScript(script, ".text(", displayClientId, ":not(:input)", clientId, index);
		}

		#endregion



	}

}